#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
#include <linux/delay.h>


#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>

#include <linux/uaccess.h>


#define MPU6500_MINORS			32	/* ... up to 256 */

static int major;

static unsigned char mpu6500_init_regs[]=
{
	0x6a, 0x10,		//106
	0x6b, 0x81,		//107
	0x68, 0x07,		//104
	0x19, 0x07,		//25
	0x6b, 0x01,		//107
	0x1c, 0x18,		//28
	0x1b, 0x18,		//27
	0x75 | 0x80, 0x70,		//	117	read id: 0x70
};

static DECLARE_BITMAP(minors, MPU6500_MINORS);

struct mpu6500_data {
	dev_t				devt;
	spinlock_t			spi_lock;
	struct spi_device	*spi;
	struct list_head	device_entry;
	/* TX/RX buffers are NULL unless this device is open */
	struct mutex	buf_lock;
	unsigned char	*tx_buffer;
	unsigned char	*rx_buffer;
	unsigned int	speed_hz;
};

static LIST_HEAD(device_list);
static DEFINE_MUTEX(device_list_lock);

static ssize_t mpu6500_sync(struct mpu6500_data *spidev, struct spi_message *message)
{
	int status;
	struct spi_device *spi;

	spin_lock_irq(&spidev->spi_lock);
	spi = spidev->spi;
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		status = -ESHUTDOWN;
	else
		status = spi_sync(spi, message);

	if (status == 0)
		status = message->actual_length;

	return status;
}

static inline ssize_t mpu6500_sync_write(struct mpu6500_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->tx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return mpu6500_sync(spidev, &m);
}

static int mpu6500_message(struct mpu6500_data *spidev,
		struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
	struct spi_message	msg;
	struct spi_transfer	*k_xfers;
	struct spi_transfer	*k_tmp;
	struct spi_ioc_transfer *u_tmp;
	unsigned		n, total, tx_total, rx_total;
	u8			*tx_buf, *rx_buf;
	int			status = -EFAULT;

	//printk("%s - %s - %d", __FILE__, __FUNCTION__, __LINE__);
	
	spi_message_init(&msg);
	k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
	if (k_xfers == NULL)
		return -ENOMEM;

	/* Construct spi_message, copying any tx data to bounce buffer.
	 * We walk the array of user-provided transfers, using each one
	 * to initialize a kernel version of the same transfer.
	 */
	tx_buf = spidev->tx_buffer;
	rx_buf = spidev->rx_buffer;
	total = 0;
	tx_total = 0;
	rx_total = 0;
	for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
			n;
			n--, k_tmp++, u_tmp++) {
		/* Ensure that also following allocations from rx_buf/tx_buf will meet
		 * DMA alignment requirements.
		 */
		unsigned int len_aligned = ALIGN(u_tmp->len, ARCH_KMALLOC_MINALIGN);

		k_tmp->len = u_tmp->len;

		total += k_tmp->len;
		/* Since the function returns the total length of transfers
		 * on success, restrict the total to positive int values to
		 * avoid the return value looking like an error.  Also check
		 * each transfer length to avoid arithmetic overflow.
		 */
		if (total > INT_MAX || k_tmp->len > INT_MAX) {
			status = -EMSGSIZE;
			goto done;
		}

		if (u_tmp->rx_buf) {
			/* this transfer needs space in RX bounce buffer */
			rx_total += len_aligned;
			k_tmp->rx_buf = rx_buf;
			rx_buf += len_aligned;
		}
		if (u_tmp->tx_buf) {
			/* this transfer needs space in TX bounce buffer */
			tx_total += len_aligned;
			k_tmp->tx_buf = tx_buf;
			if (copy_from_user(tx_buf, (const u8 __user *)
						(uintptr_t) u_tmp->tx_buf,
					u_tmp->len))
				goto done;
			tx_buf += len_aligned;
		}

		k_tmp->cs_change = !!u_tmp->cs_change;
		k_tmp->tx_nbits = u_tmp->tx_nbits;
		k_tmp->rx_nbits = u_tmp->rx_nbits;
		k_tmp->bits_per_word = u_tmp->bits_per_word;
		k_tmp->delay_usecs = u_tmp->delay_usecs;
		k_tmp->speed_hz = u_tmp->speed_hz;
		if (!k_tmp->speed_hz)
			k_tmp->speed_hz = spidev->speed_hz;

		spi_message_add_tail(k_tmp, &msg);
	}

	status = mpu6500_sync(spidev, &msg);
	if (status < 0)
		goto done;

	/* copy any rx data out of bounce buffer */
	for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
			n;
			n--, k_tmp++, u_tmp++) {
		if (u_tmp->rx_buf) {
			if (copy_to_user((u8 __user *)
					(uintptr_t) u_tmp->rx_buf, k_tmp->rx_buf,
					u_tmp->len)) {
				status = -EFAULT;
				goto done;
			}
		}
	}
	status = total;

done:
	kfree(k_xfers);
	return status;
}

static struct spi_ioc_transfer *
mpu6500_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc, unsigned *n_ioc)
{
	u32	tmp;

	//printk("%s - %s - %d", __FILE__, __FUNCTION__, __LINE__);
	
	/* Check type, command number and direction */
	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC
			|| _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
			|| _IOC_DIR(cmd) != _IOC_WRITE)
		return ERR_PTR(-ENOTTY);

	tmp = _IOC_SIZE(cmd);
	if ((tmp % sizeof(struct spi_ioc_transfer)) != 0)
		return ERR_PTR(-EINVAL);
	*n_ioc = tmp / sizeof(struct spi_ioc_transfer);
	if (*n_ioc == 0)
		return NULL;

	/* copy into scratch area */
	return memdup_user(u_ioc, tmp);
}

static long mpu6500_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int			retval = 0;
	struct mpu6500_data	*spidev;
	struct spi_device	*spi;
	unsigned		n_ioc;
	struct spi_ioc_transfer	*ioc;

	/* Check type and command number */
	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
		return -ENOTTY;

	/* guard against device removal before, or while,
	 * we issue this ioctl.
	 */
	spidev = filp->private_data;
	spin_lock_irq(&spidev->spi_lock);
	spi = spi_dev_get(spidev->spi);
	spin_unlock_irq(&spidev->spi_lock);

	if (spi == NULL)
		return -ESHUTDOWN;
	
	/* use the buffer lock here for triple duty:
	 *  - prevent I/O (from us) so calling spi_setup() is safe;
	 *  - prevent concurrent SPI_IOC_WR_* from morphing
	 *    data fields while SPI_IOC_RD_* reads them;
	 *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
	 */
	mutex_lock(&spidev->buf_lock);
	
	ioc = mpu6500_get_ioc_message(cmd, (struct spi_ioc_transfer __user *)arg, &n_ioc);
	if (IS_ERR(ioc)) {
		retval = PTR_ERR(ioc);
	}

	/* translate to spi_message, execute */
	retval = mpu6500_message(spidev, ioc, n_ioc);
	kfree(ioc);
		
	mutex_unlock(&spidev->buf_lock);
	spi_dev_put(spi);
	return retval;
}

static void mpu6500_hwInit(struct mpu6500_data *spidev)
{
	memcpy(spidev->tx_buffer, mpu6500_init_regs, 2);
	mpu6500_sync_write(spidev, 2);
	msleep(100);

	memcpy(spidev->tx_buffer, &mpu6500_init_regs[2], 2);
	mpu6500_sync_write(spidev, 2);
	msleep(100);

	memcpy(spidev->tx_buffer, &mpu6500_init_regs[4], 2);
	mpu6500_sync_write(spidev, 2);
	msleep(100);

	memcpy(spidev->tx_buffer, &mpu6500_init_regs[6], 2);
	mpu6500_sync_write(spidev, 2);

	memcpy(spidev->tx_buffer, &mpu6500_init_regs[8], 2);
	mpu6500_sync_write(spidev, 2);

	memcpy(spidev->tx_buffer, &mpu6500_init_regs[10], 2);
	mpu6500_sync_write(spidev, 2);

	memcpy(spidev->tx_buffer, &mpu6500_init_regs[12], 2);
	mpu6500_sync_write(spidev, 2);

}

static int mpu6500_open(struct inode *inode, struct file *filp)
{
	struct mpu6500_data	*spidev;
	int			status = -ENXIO;
	
	mutex_lock(&device_list_lock);

	list_for_each_entry(spidev, &device_list, device_entry) {
		if (spidev->devt == inode->i_rdev) {
			status = 0;
			break;
		}
	}

	if (status) {
		pr_debug("spidev: nothing for minor %d\n", iminor(inode));
		goto err_find_dev;
	}

	if (!spidev->tx_buffer) {
		spidev->tx_buffer = kmalloc(1024, GFP_KERNEL);
		if (!spidev->tx_buffer) {
			dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
			status = -ENOMEM;
			goto err_find_dev;
		}
	}

	if (!spidev->rx_buffer) {
		spidev->rx_buffer = kmalloc(1024, GFP_KERNEL);
		if (!spidev->rx_buffer) {
			dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
			status = -ENOMEM;
			goto err_alloc_rx_buf;
		}
	}

	filp->private_data = spidev;
	nonseekable_open(inode, filp);

	mutex_unlock(&device_list_lock);
	
	mpu6500_hwInit(spidev);
	
	return 0;

err_alloc_rx_buf:
	kfree(spidev->tx_buffer);
	spidev->tx_buffer = NULL;
err_find_dev:
	mutex_unlock(&device_list_lock);
	return status;
}

static const struct file_operations mpu6500_fops = {
	.owner =	THIS_MODULE,
	//mpu6500_ioctl 设置或者获取设备的工作参数
	.unlocked_ioctl = mpu6500_ioctl,
	.open			= mpu6500_open,
};


static struct class *mpu6500_class;

static const struct of_device_id mpu6500_dt_ids[] = {
	{ .compatible = "InvenSense,mpu6500" },
	{},
};

static int mpu6500_probe(struct spi_device *spi)
{
	struct mpu6500_data	*spidev;
	int					status;
	unsigned long		minor;

	struct resource *maddr;
	void __iomem * vaddr;

	//申请e8开始的40个字节，也就是要操作的四个spi的管脚
	maddr = request_mem_region(0x32b300e8, 40, " ");
	vaddr = ioremap(maddr->start, 8);
	//四个管脚都配置为驱动力4, 可选功能选择0就可以了
	writel(0x40, vaddr);
	writel(0x40, vaddr + 4);
	writel(0x40, vaddr + 8);
	writel(0x40, vaddr + 12);
	
	
	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;

	/* Initialize the driver data */
	spidev->spi = spi;
	//初始化自旋锁
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);
	//初始化设备的头节点
	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, MPU6500_MINORS);
	if (minor < MPU6500_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(major, minor);
		//创建spi设备的设备节点文件 
		dev = device_create(mpu6500_class, &spi->dev, spidev->devt,
				    spidev, "mpu6500.%d.%d",
				    spi->master->bus_num, spi->chip_select);
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	return status;
}

static int mpu6500_remove(struct spi_device *spi)
{
	struct mpu6500_data	*spidev = spi_get_drvdata(spi);
	release_mem_region(0x32b300e8, 40);

	/* prevent new opens */
	mutex_lock(&device_list_lock);
	/* make sure ops on existing fds can abort cleanly */
	spin_lock_irq(&spidev->spi_lock);
	spidev->spi = NULL;
	spin_unlock_irq(&spidev->spi_lock);

	list_del(&spidev->device_entry);
	device_destroy(mpu6500_class, spidev->devt);
	clear_bit(MINOR(spidev->devt), minors);

	kfree(spidev);
	mutex_unlock(&device_list_lock);

	return 0;
}

static struct spi_driver mpu6500_spi_driver = {
	.driver = {
		.name =		"mpu6500",
		.of_match_table = of_match_ptr(mpu6500_dt_ids),
	},
	.probe =	mpu6500_probe,
	.remove =	mpu6500_remove,
};

/*-------------------------------------------------------------------------*/

static int __init mpu6500_init(void)
{
	int status;

	//注册cdev对象
	major = register_chrdev(0, "spi", &mpu6500_fops);
	//注册设备类
	mpu6500_class = class_create(THIS_MODULE, "mpu6500");
	
	//注册spi的drv到内核
	status = spi_register_driver(&mpu6500_spi_driver);
	return status;
}

static void __exit mpu6500_exit(void)
{
	spi_unregister_driver(&mpu6500_spi_driver);
	class_destroy(mpu6500_class);
	unregister_chrdev(major, mpu6500_spi_driver.driver.name);
}

module_init(mpu6500_init);
module_exit(mpu6500_exit);

MODULE_AUTHOR("andy lau");
MODULE_LICENSE("GPL");
